﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Notoric.Commanding.CommandBase;
using System.Globalization;
using System.Linq;

namespace Notoric.ViewModel.Elements.CWNotation
{
    /// <summary>
    /// Adapts a <see cref="Model.Elements.CWNotation.DurationalSymbol"/> instance to a Windows Presentation Foundation view.
    /// </summary>
    public abstract class DurationalSymbol : CoreSymbol
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="DurationalSymbol"/> instance.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="adaptedElement">
        /// <para>
        /// Type: <see cref="Model.Elements.CWNotation.DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The adapted model element instance.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedElement"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="NullReferenceException">
        /// <paramref name="owner"/> is <see langword="null"/>.
        /// The values of the <see cref="ViewModelElement.UndoStack"/> and <see cref="ViewModelElement.RedoStack"/> properties
        /// are passed directly to the base class (<see cref="ViewModelElement"/>) constructor. Thus, there is not
        /// an occasion to react with a more appropriate exception.
        /// </exception>
        public DurationalSymbol(Staff owner, Model.Elements.CWNotation.DurationalSymbol adaptedElement)
            : base(owner, adaptedElement)
        {
            dotsCollection = new ObservableCollection<AugmentationDot>();
            dots = new ReadOnlyObservableCollection<AugmentationDot>(dotsCollection);

            for (int i = 0; i < adaptedElement.AugmentationDots; ++i)
                dotsCollection.Add(new AugmentationDot(this, i));
        }

        #endregion

        #region ViewModelElement Members

        /// <inheritdoc/>
        public override void Dispose()
        {
            foreach (var dot in dotsCollection)
                dot.Dispose();
            base.Dispose();
        }

        #endregion

        #region Event Handlers

        /// <inheritdoc/>
        protected override bool AdaptedElement_PropertyChanged(PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "AugmentationDots")
            {
                var adaptedSymbol = (Model.Elements.CWNotation.DurationalSymbol)AdaptedElement;

                if (adaptedSymbol.AugmentationDots > dotsCollection.Count)
                {
                    for (int i = dotsCollection.Count; i < adaptedSymbol.AugmentationDots; ++i)
                        dotsCollection.Add(new AugmentationDot(this, i));
                }
                else
                {
                    for (int i = dotsCollection.Count; i > adaptedSymbol.AugmentationDots; --i)
                        dotsCollection.RemoveAt(i - 1);
                }

                return true;
            }
            else if (e.PropertyName == "BasicDuration")
            {
                RaisePropertyChanged("CoreWidth");
                RaisePropertyChanged("Width");
            }
            else if (e.PropertyName == "GroupBeam")
            {
                if (((Model.Elements.CWNotation.DurationalSymbol)AdaptedElement).GroupBeam == null)
                    beam = null;
            }
            
            return base.AdaptedElement_PropertyChanged(e);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates an undoable command for setting the <see cref="BasicDuration"/> of the symbol.
        /// </summary>
        /// <param name="duration">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The new basic duration of the symbol.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for use.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetBasicDurationCommand(ushort duration)
        {
            return new SetterCommand<ushort>(this, "BasicDuration", duration, UndoStack, RedoStack);
        }

        /// <summary>
        /// Creates an undoable command for setting the number of <see cref="AugmentationDots"/>.
        /// </summary>
        /// <param name="count">
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The new number of augmentation dots.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetDotsCountCommand(byte count)
        {
            return new SetterCommand<byte>(this, "AugmentationDots", count, UndoStack, RedoStack);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the collection of augmentation dots.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyObservableCollection{T}"/> of <see cref="AugmentationDot"/>
        /// </para>
        /// <para>
        /// The collection of augmentation dots belonging to this symbol.
        /// </para>
        /// </value>
        public ReadOnlyObservableCollection<AugmentationDot> AugmentationDots
        {
            get { return dots; }
        }

        /// <summary>
        /// Gets or sets the basic duration of the symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The duration of the symbol without the augmentation dots.<br/>
        /// This value must not be 0.
        /// </para>
        /// </value>
        public ushort BasicDuration
        {
            get 
            {
                if (UnsavedChanges.ContainsKey("BasicDuration"))
                {
                    var cmd = (SetterCommand<ushort>)UnsavedChanges["BasicDuration"];
                    return cmd.Value;
                }
                
                return ((Model.Elements.CWNotation.DurationalSymbol)AdaptedElement).BasicDuration; 
            }
            set
            {
                if (UnsavedChanges.ContainsKey("BasicDuration"))
                {
                    var cmd = (SetterCommand<ushort>)UnsavedChanges["BasicDuration"];
                    cmd.Value = value;
                }
                else
                    UnsavedChanges["BasicDuration"] = CreateSetBasicDurationCommand(value);

                RaisePropertyChanged("BasicDuration");
            }
        }

        /// <summary>
        /// Gets the core width of the symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The width (in device independent pixels) of the symbol without the augmentation dots.
        /// </para>
        /// </value>
        public double CoreWidth
        {
            get { return (double)LengthConverter.Convert(((Model.Elements.CWNotation.DurationalSymbol)AdaptedElement).GetCoreWidth(), typeof(double), null, CultureInfo.CurrentCulture); }
        }

        /// <summary>
        /// Gets the offset of the first augmentation dots from the symbol's position.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the first augmentation dot
        /// from the <see cref="CoreSymbol.Position"/> of the symbol.
        /// </para>
        /// </value>
        public abstract double DotsOffset
        {
            get;
        }

        /// <summary>
        /// Gets the beam of the group that this symbol belongs to.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Beam"/>
        /// </para>
        /// <para>
        /// The beam of the group that this symbol belongs to.
        /// A <see langword="null"/> value means that this symbol is not a part of any group.
        /// </para>
        /// </value>
        public Beam GroupBeam
        {
            get
            {
                if (beam == null && ((Model.Elements.CWNotation.DurationalSymbol)AdaptedElement).GroupBeam != null)
                {
                    var modelBeam = ((Model.Elements.CWNotation.DurationalSymbol)AdaptedElement).GroupBeam;
                    var staff = modelBeam.FindOwner<Model.Elements.CWNotation.Staff>();
                    var vmStaff = FindOwner<System>().Staves.First(s => s.AdaptedElement == staff);

                    beam = vmStaff.Attachments.First(a => a.AdaptedElement == modelBeam) as Beam;
                }

                return beam;
            }
        }

        /// <summary>
        /// Gets or sets the number of the voice to which the symbol belongs.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The number of the voice to which the symbol belongs.
        /// </para>
        /// </value>
        public byte Voice
        {
            get { return ((Model.Elements.CWNotation.DurationalSymbol)AdaptedElement).PresentedElement.Voice.Number; }
            set { throw new NotImplementedException(); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="GroupBeam"/> property.
        /// </summary>
        private Beam beam;
        /// <summary>
        /// Holds the value of the <see cref="AugmentationDots"/> property.
        /// </summary>
        private ReadOnlyObservableCollection<AugmentationDot> dots;
        /// <summary>
        /// Holds the collection of augmentation dots.
        /// </summary>
        private ObservableCollection<AugmentationDot> dotsCollection;
        
        #endregion
    }
}
